#!/bin/sh
#
# run -- Run the docker image of the lab
#
# Copyright (C) 2016-2021 Wu Zhangjin <falcon@ruma.tech>
#

echo "LOG: Init docker environment ..."

TOP_DIR="$(cd "$(dirname "$0")"/../../ && pwd)"
. "$TOP_DIR"/tools/docker/config >/dev/null

# Make sure the lock is removed when this script is exited
LOCK_FILE=/var/lock/cloud-lab-runner.lock
trap "rm -rf ${LOCK_FILE}" QUIT INT TERM EXIT

# Start Locking
if ! mkdir $LOCK_FILE 2>/dev/null; then
  echo "ERR: It is already running ..." >&2
  exit 1
fi

# Check and download the lab source
log_print "Check source code of $LAB_NAME ..."
if [ ! -f "$TOP_DIR"/labs/$LAB_NAME/.git ]; then
  info_print "Pull git repo: $LAB_NAME"
  "$DOCKER_CHOOSE_CMD" $LAB_NAME
fi

# Notify for X11 system
which notify-send >/dev/null 2>&1
if [ $? -eq 0 ]; then
  notice_summary="$LAB_NAME is loading ..."
  notice_body="Welcome to the world of $LAB_NAME!"
  if [ -f "$LAB_LOGO" ]; then
    notify-send -i "$LAB_LOGO" "$notice_summary" "$notice_body"
  else
    notify-send "$notice_summary" "$notice_body"
  fi
fi

# Clean up old stopped containers if the saved environment has been cleaned up
log_print "Check obsoleted containers ..."
if [ ! -f "$LAB_CONTAINER_ID" ]; then
  for status in exited created
  do
    unused_containers="$(docker ps -a -f ancestor=$IMAGE -f status=$status -q)"
    [ -n "$unused_containers" ] && docker rm -f "$unused_containers"
  done
fi

# Prepare container net
log_print "Check networking ..."
prepare_cloud_lab_net

# Start the transparent proxy via tinylab/cloud-ubuntu-proxy_client_transparent container
if [ $TPROXY -eq 1 ]; then
  log_print "Prepare transparant proxy service ..."
  tproxy_id=$(docker ps -f name=$TPROXY_NAME -f status=exited -q)
  [ -n "$tproxy_id" ] && info_print "Resume the stopped $TPROXY_NAME" && docker start $tproxy_id

  tproxy_id=$(docker ps -f name=$TPROXY_NAME -f status=running -q)
  if [ -z "$tproxy_id" ]; then
    docker rm -f $TPROXY_NAME 2>/dev/null

    if [ -n "$PROXY_SERVER" -a -n "$PROXY_PWD" -a -n "$ENCRYPT_CMD" ]; then
      info_print "Start $TPROXY_NAME"
      TPROXY_IP=$TPROXY_IP PROXY_SERVER="$PROXY_SERVER" PROXY_PWD="$PROXY_PWD" ENCRYPT_CMD="$ENCRYPT_CMD" PROXY_LIMIT=$PROXY_LIMIT "$DOCKER_TPROXY_CMD" $*
      [ $? -ne 0 ] && err_print "Failed to run $TPROXY_NAME" >&2 && exit 1
    fi
  fi

  TPROXY_IP=`docker inspect --format '{{ .NetworkSettings.IPAddress }}' $TPROXY_NAME`
  do_unlock
  set_var TPROXY_IP
  do_lock
fi

# Start the ssh/vnc proxy via tinylab/cloud-ubuntu-web container
log_print "Prepare web proxy service ..."
wproxy_id=$(docker ps -f name=$WPROXY_NAME -f status=exited -q)
if [ -n "$wproxy_id" ]; then
  info_print "Resume the stopped $WPROXY_NAME"
  prepare_and_touch_export_files
  docker start $wproxy_id
fi

wproxy_id=$(docker ps -f name=$WPROXY_NAME -f status=running -q)
if [ -z "$wproxy_id" ]; then
  docker rm -f $WPROXY_NAME 2>/dev/null

  info_print "Start $WPROXY_NAME"
  prepare_and_touch_export_files
  WPROXY_IP=$WPROXY_IP "$DOCKER_EXPORT_CMD" $*
  [ $? -ne 0 ] && err_print "Failed to run $WPROXY_NAME" >&2 && exit 1
fi

if [ -n "$CONTAINER_NAME" ]; then
  # If already there and running, open it
  log_print "Check status of $CONTAINER_NAME"
  lab_id=$(docker ps -f name=$CONTAINER_NAME -f status=running -q)
  if [ -n "$lab_id" ]; then
    "$DOCKER_PUBLISH_CMD" $*

    if [ "$LOGIN" != "none" ]; then
      info_print "Open the running $LAB_NAME"
      rm -rf $LOCK_FILE
      [ "$LOGIN" != "none" ] && LOGIN=$LOGIN "$DOCKER_LOGIN_CMD" $*
    fi
    exit $?
  fi

  # If already there and stopped, start and open it
  lab_id=$(docker ps -f name=$CONTAINER_NAME -f status=exited -q)
  if [ -n "$lab_id" ]; then
    info_print "Resume the stopped $LAB_NAME"
    "$DOCKER_START_CMD" $LAB_NAME
    info_print "Wait for lab resuming..."
    while :; do
      boot_completed=`docker logs $CONTAINER_NAME 2>/dev/null | grep boot_completed`;
      sleep 2;
      [ -n "$boot_completed" ] && break
    done

    "$DOCKER_PUBLISH_CMD" $*
    rm -rf $LOCK_FILE
    [ "$LOGIN" != "none" ] && LOGIN=$LOGIN "$DOCKER_LOGIN_CMD" $*

    exit $?
  fi

  lab_id=$(docker ps -f name=$CONTAINER_NAME -f status=running -q)
  [ -z "$lab_id" ] && docker rm -f $CONTAINER_NAME 2>/dev/null

else
  log_print "Generate container name"
  DEF_UUID_LEN=6 && UUID=$(echo "$TOP_DIR" | $DEF_ENCRYPT_CMD | cut -c 1-$DEF_UUID_LEN)

  while :;
  do
    CONTAINER_NAME=${LAB_NAME}-`get_random`-$UUID
    ids=$(docker ps -a -q -f name=$CONTAINER_NAME)
    [ -z "$ids" ] && break
  done
fi

# Require to prepare some environment for docker containers in host
if [ -f "$LAB_HOST_RUN" ]; then
  log_print "Run host side init for $LAB_NAME"
  "$LAB_HOST_RUN"
fi

# Run the lab via start a lab container
caps=""
dnss=""
devs=""
vars=""
envs=""

# Sync UID before running
[ "x$HOST_OS" = "xLinux" ] && UNIX_UID=$(id -u `get_host_user`)
[ "x$UNIX_UID" = "x0" -o -z "$UNIX_UID" ] && UNIX_UID=$DEF_UNIX_UID
[ -z "$UNIX_USER" ] && UNIX_USER=$DEF_UNIX_USER
[ ! -d $CONFIG_DIR/home ] && mkdir -p $CONFIG_DIR/home

# Fixed up for docker toolbox in windows
# ref: https://headsigned.com/posts/mounting-docker-volumes-with-docker-toolbox-for-windows/
volumemap="-v '/$GIT_DIR':'$GIT_WORKDIR'"
volumemap="$volumemap -v '/$LAB_DIR':'$LAB_WORKDIR'"
volumemap="$volumemap -v '/$TOOL_DIR/docker/container':'$TOOL_WORKDIR/docker/container':ro"
volumemap="$volumemap -v '/$TOOL_DIR/lab/run':'$TOOL_WORKDIR/lab/run':ro"
volumemap="$volumemap -v '/$TOOL_DIR/system':'$TOOL_WORKDIR/system':ro"
volumemap="$volumemap -v '/$CONFIG_DIR/tools':'$CONFIG_WORKDIR/tools':ro"
volumemap="$volumemap -v '/$CONFIG_DIR/system':'$CONFIG_WORKDIR/system':ro"
volumemap="$volumemap -v '/$CONFIG_DIR/home':'//home/':rw"
volumemap="$volumemap -v '/$CONFIG_DIR/lab-logo.png':'$CONFIG_WORKDIR/lab-logo.png':ro"
volumemap="$volumemap -v '/$CONFIG_DIR/docker/container':'$CONFIG_WORKDIR/docker/container'"
limits=$LIMITS

# Print shared volumes
info_print "Shared volumes:"
info_print "'/$CONFIG_DIR/home' --> '//home'"
info_print "'/$LAB_DIR' --> '$LAB_WORKDIR'"

# Get a new ip for our new container if no one cached
if [ -z "$VNC_IP" -o "$VNC_IP" = "$DEF_VNC_IP" ]; then
  log_print "Get a new ip address"
  VNC_IP=`VNC_NET=$VNC_NET IP_USED="$VNC_GATEWAY $WPROXY_IP $TPROXY_IP" "$DOCKER_IP_CMD"`
  [ $? -ne 0 ] && err_print "\"$DOCKER_IP_CMD\": get ip address error." >&2 && exit 1
fi

# Get screen size if no one cached
log_print "Get screen size"
get_var SCREEN_SIZE
[ -z "$SCREEN_SIZE" ] && get_screen_size

# Build vars passed to container
log_print "Build variables for $CONTAINER_NAME"

# Init environment variables instead of parsing it in container
get_vars UNIX_IDENTIFY SUDO_IDENTIFY VNC_IDENTIFY DESKTOP

for var in $VARS; do
    # Available encrypt cmds: sha1sum, sha224sum, cksum, sha256sum, sha512sum, md5sum, sha384sum, sum
    value=$(eval echo \$${var})

    # Ignore empty setting
    [ -z "$value" ] && continue

    # Maximize the screen size of container, allow resize to what user want
    if [ "$var" = "SCREEN_SIZE" ]; then
      get_host_virtual

      if [ $HOST_VIRTUAL -eq 1 ]; then
        MAX_SCREEN_SIZE=1920x1080
        if [ $(($(echo "$SCREEN_SIZE" | tr 'x' '*'))) -gt $(($(echo "$MAX_SCREEN_SIZE" | tr 'x' '*'))) ]; then
          MAX_SCREEN_SIZE=$SCREEN_SIZE
        fi

        value=$MAX_SCREEN_SIZE
      fi
    fi
    echo "$var" | grep -q "_PWD"
    if [ $? -eq 0 -a -n "$ENCRYPT_CMD" ]; then
      vars="$vars -e $var=$(echo $value | tr -d '\n' | $ENCRYPT_CMD | cut -d' ' -f1)";
    else
      vars="$vars -e $var=$(echo $value | tr -d '\n' | cut -d' ' -f1)";
    fi
done

# Get configured settings
log_print "Build more arguments ..."
if [ "x$HOST_OS" = "xWindows" ]; then
  get_vars CAPS DEVICES VOLUMEMAP PRIV_MODE PLATFORM
else
  get_vars ENVS CAPS DNS DEVICES PORTMAP VOLUMEMAP PRIV_MODE PLATFORM
fi

for env in $ENVS; do vars="$vars -e $env"; done
for cap in $CAPS; do caps="$caps --cap-add $cap"; done
for dns in $DNS; do dnss="$dnss --dns $dns"; done
for dev in $DEVICES; do [ -e "$dev" ] && devs="$devs --device $dev"; done
for map in $PORTMAP; do portmap="$portmap -p $map"; done
for map in $VOLUMEMAP; do volumemap="$volumemap -v $map"; done

# Enable privileged mode by default
[ -z "$PRIV_MODE" ] && PRIV_MODE=1
if [ "x$PRIV_MODE" = "x1" ]; then
  warn_print "privileged mode should be disabled with PRIV_MODE=0"
  privmode=--privileged
fi

# For cross platform support
[ -n "$PLATFORM" ] && platform="--platform $PLATFORM"
# FIXME: docker toolbox is too old to support the --platform option
[ "x$HOST_OS" = "xWindows" -a "$DOCKER" = "toolbox" ] && platform=""

# Build container name
container="--name $CONTAINER_NAME"

# Only allow generate <=100M coredump
if [ "x$COREDUMP" = "x1" ]; then
  core_size_m=100
  core_size_k=$(($core_size_m * 1024 * 1024))
  coredump="--ulimit core=$core_size_k:$core_size_k"
else
  coredump="--ulimit core=0"
fi

# nofile
nofile_softlimit=524288
nofile_hardlimit=1048576
nofile="--ulimit nofile=$nofile_softlimit:$nofile_hardlimit"

# ulimits
ulimits="$coredump $nofile"

# use --ip command to record the ip address
[ -n "$VNC_IP" ] && ip="--ip=$VNC_IP"
net="$ip --network $VNC_NET_NAME"

# Audio output support
[ "x$HOST_OS" = "xLinux" ] && audio="-v //run/user/${UNIX_UID}/pulse://run/user/${UNIX_UID}/pulse"

# For security support
seccomp="--security-opt seccomp=$TOP_DIR/configs/common/seccomp-profiles-default.json"

# For /tmp automount
tmpfs="--tmpfs /tmp:rw,exec"

# For lab name
lab_name="-h $LAB_NAME"

info_print "Wait for lab launching ..."

lab_id=$(eval docker run -d $lab_name $privmode $ulimits $tmpfs $seccomp $platform $net $audio $container $portmap $caps $dnss $devs $limits $volumemap $vars $EXTRA_ARGS $IMAGE)

[ $? -ne 0 ] && err_print "docker running error." && exit 1

CONTAINER_ID=`echo $lab_id | cut -c-12`

trap 'echo "  Please wait for a while, to really stop me, please press 'CTRL+z' and fire: kill -9 $$"' 2

# Set boot timeout, 1 minute should be enough for modern computer
[ -z "$BOOT_TIMEOUT" ] && BOOT_TIMEOUT=60

boot_time=0
while :; do
    boot_completed=`docker logs $CONTAINER_NAME 2>/dev/null | grep boot_completed`
    [ -n "$boot_completed" ] && break

    boot_time=`expr $boot_time + 1`
    echo ".... $boot_time / $BOOT_TIMEOUT"

    if [ $boot_time -gt $BOOT_TIMEOUT ]; then
       err_print "Boot $CONTAINER_NAME Timeout, exit."
       echo
       err_print "Boot log is below:"
       echo
       docker logs $CONTAINER_NAME
       echo
       err_print "Remove not completed $CONTAINER_NAME"
       docker rm -f $CONTAINER_NAME
       exit 1
    fi

    sleep 1
done

do_unlock

# Get Host ip for !Linux system (the ip address of the virtualbox machine)
get_host

# Save the lab's information (for restore the container for 'docker start')
set_vars CONTAINER_NAME CONTAINER_ID SCREEN_SIZE
[ -n "$PRIV_MODE" -a "x$PRIV_MODE" = "x1" ] && set_var PRIV_MODE
[ "$MIRROR_SITE" != "$DEF_MIRROR_SITE" ] && set_var MIRROR_SITE
[ "$UNIX_USER" != "$DEF_UNIX_USER" ] && set_var UNIX_USER
[ "$UNIX_UID" != "1000" -a "$UNIX_UID" != "0" ] && set_var UNIX_UID
[ -n "$DESKTOP" -a "$DESKTOP" != "$DEF_DESKTOP" ] && set_var DESKTOP
[ -n "$VNC_TOKEN" ] && set_var VNC_TOKEN

do_lock

trap 'exit 0' 2

info_print "Container ID: ${CONTAINER_ID} Container Name: ${CONTAINER_NAME}"
password=`docker logs $CONTAINER_NAME 2>/dev/null | grep Password`
info_print "$password"

# Publish vnc port
"$DOCKER_PUBLISH_CMD" $*

# It is ok to release the lock file now
rm -rf $LOCK_FILE

[ "$LOGIN" != "none" ] && LOGIN=$LOGIN "$DOCKER_LOGIN_CMD" $*

# This is very slow in windows, silence it
# It is not necessary for normal users
("$DOCKER_RELEASE_CMD" all >/dev/null 2>&1)&
